<?php

// | Copyright (c) 2019~2020 http://jiunidu.com All rights reserved.
// +----------------------------------------------------------------------
// | Author: huanglijun <80792275@qq.com>
// +----------------------------------------------------------------------

namespace service\pay;

use think\facade\Log;
use model\BaseModel;
use think\facade\Config;
use think\Db;

class VirtualPay extends Basemodel
{
       //插入记录，返回最后记录的ID;
       protected $insert_id                    = -1; 
       //账户表       
       protected $user_table                   = 'user'; 
       //账户身份关联表    
       protected $user_identity_join_table     = 'user_identity_join';
       //账户身份表   
       protected $user_identity_table          = 'user_identity';  
       //账户余额表
       protected $user_balance_table           = 'user_balance'; 
       //账户余额类型表
       protected $user_balance_type_table      = 'user_balance_type'; 
       //账户金币表
       protected $user_coin_table              = 'user_coin'; 
       //账户金币类型表
       protected $user_coin_type_table         = 'user_coin_type'; 
       //账户等级表   
       protected $user_grade_table             = 'user_grade';    
       //余额交易类型
       protected $user_balance_type = [
           'buy_goods'   =>  1,                     //购买商品或者服务扣除
           'buy_goods_returned'   =>  3,            //商品或者服务返还
           'from_coupon' =>  2,                     //优惠券赠送   
           'pick_up_income' =>  4,                  //提货收益增加   
           'rebate_income' =>  5,                   //粉丝返利收益   
           'rebate_lock' =>  6,                     //返利锁定   
           'rebate_unlock' =>  7,                   //返利解锁   
           'service_add' =>  8,                     //商家服务收益增加   
           'buy_add' =>  9,                         //线下购收益增加   
           'bonus' =>  10,                          //平台分红   
           'cash_out' =>  11,                       //商家提现扣除
           'cash_in' =>  12,                        //商家提现返回   
        ];
       //金币交易类型
       protected $user_coin_type = [
            'buy_goods'   =>  6,                    //购买商品
            'from_coupon' =>  7,                    //优惠券赠送   
            'buy_goods_returned' =>  8,             //商品订单退还金币 
            'survey' =>  9,                         //问卷调查
            'rebate_income' =>  10,                 //粉丝返利收益  
            'rebate_lock' =>  11,                   //返利锁定   
            'rebate_unlock' =>  12,                 //返利解锁   
            'exchange_coin'=>13,                    //兑换金币
            
        ];

    /**
     * 更新余额、金币，供外部使用
     * @param   int        $user_id                 用户ID
     * @param   int        $user_identity_id        身份关联表ID,可以为空
     * @param   int        $type                    流向 1增加 2 消耗
     * @param   int        $pay_type                支付类型，1 余额 2金币 3余额与金币
     * @param   float      $balance_num             余额数 单位:元
     * @param   float      $coin_num                金币数 单位:元
     * @param   int        $oper_type               操作类型，1 商品或者服务 2 问卷调查 3 返利收益  4 提货收益增加  5 商家服务收益增加 6 线下购收益增加 7平台分红 8 兑换金币 9 提现
     * @param   string     $table_id                订单号，或者表名
     * @param   string     $remark                  交易描述
     * @return  int                                 返回记录ID     返回-1为失败了          
     */
    public function public_update_user_virtual($user_id=0,$user_identity_id=0,$type=0,$pay_type=0,$balance_num=0,$coin_num=0,$oper_type=0,$table_id='',$remark="")
    {
        return $this->update_user_virtual($user_id,$user_identity_id,$type,$pay_type,$balance_num,$coin_num,$oper_type,$table_id,$remark);
    }
        
    /**
     * 更新余额、金币
     * @param   int        $user_id                 用户ID
     * @param   int        $user_identity_id        身份关联表ID
     * @param   int        $type                    流向 1增加 2 消耗
     * @param   int        $pay_type                支付类型，1 余额 2金币 3余额与金币
     * @param   float      $balance_num             余额数
     * @param   float      $coin_num                金币数
     * @param   int        $oper_type               操作类型，1 商品或者服务 2 问卷调查 3 返利收益  4 提货收益增加  5 商家服务收益增加 6 线下购收益增加 7平台分红 8兑换金币  9 提现  
     * @param   string     $table_id                订单号，或者表名
     * @param   string     $remark                  交易描述
     * @return  int                                 返回记录ID     返回-1为失败了          
     */
    public function update_user_virtual($user_id=0,$user_identity_id=0,$type=0,$pay_type=0,$balance_num=0,$coin_num=0,$oper_type=0,$table_id='',$remark="")
    {
       
        $insert_id  = -1;
        if(empty($user_id)||empty($pay_type)||empty($type))
        {
            return $insert_id;
        }
        //配置项目类型说明
        $balance_type=0;
        $coin_type=0;
        if($type==1 && $oper_type==1)
        {
            $balance_type=$this->user_balance_type['buy_goods_returned'];
            $coin_type=$this->user_coin_type['buy_goods_returned'];
        }
        if($type==2 && $oper_type==1)
        {
            $balance_type=$this->user_balance_type['buy_goods'];
            $coin_type=$this->user_coin_type['buy_goods'];
        }
        //问卷检查奖励金币
        if($type==1 && $oper_type==2)
        {
            $coin_type=$this->user_coin_type['survey'];
        }
        //粉丝购物奖励返利
        if($type==1 && $oper_type==3)
        {
            $balance_type=$this->user_balance_type['rebate_income'];
            $coin_type=$this->user_coin_type['rebate_income'];
        }
        //提货收益增加
        if($type==1 && $oper_type==4)
        {
            $balance_type=$this->user_balance_type['pick_up_income'];
        }
        //商家服务收益增加 
        if($type==1 && $oper_type==5)
        {
            $balance_type=$this->user_balance_type['service_add'];
        }
        //线下购收益增加
        if($type==1 && $oper_type==6)
        {
            $balance_type=$this->user_balance_type['buy_add'];
        }
        //平台分红
        if($type==1 && $oper_type==7)
        {
            $balance_type=$this->user_balance_type['bonus'];
        }
        //兑换金币
        if($type==1 && $oper_type==8)
        {
            $coin_type=$this->user_coin_type['exchange_coin'];
        }
        //商家提现扣除
        if($type==2 && $oper_type==9)
        {
            $balance_type=$this->user_balance_type['cash_out'];
        }
        //商家提现返回
        if($type==1 && $oper_type==9)
        {
            $balance_type=$this->user_balance_type['cash_in'];
        }

        //支付方式判别
        if(!in_array((int)$pay_type,[1,2,3]))
        {
            return -1; 
        }
        
        Db::startTrans();
        try{
            
            /** 余额操作 */
            if($pay_type===1)
            {
                $yesno=$this->update_user_balance($user_id,$type,$balance_num,$table_id,$balance_type,$remark);
                if(!$yesno)
                {
                    throw new \Exception('更新用户虚拟财务失败，回滚操作');
                }
            }

            /** 金币操作 */
            if($pay_type===2)
            {
                $yesno=$this->update_user_coin($user_id,$type,$coin_num,$table_id,$coin_type,$remark);
                if(!$yesno)
                {
                    throw new \Exception('更新用户虚拟财务失败，回滚操作');
                }
            }

             /** 余额金币同时操作 */
            if($pay_type===3)
            {
                $yesno_balance=$this->update_user_balance($user_id,$type,$balance_num,$table_id,$balance_type,$remark);
                $yesno_coin=$this->update_user_coin($user_id,$type,$coin_num,$table_id,$coin_type,$remark);
                if(!$yesno_balance || !$yesno_coin)
                {
                    throw new \Exception('更新用户虚拟财务失败，回滚操作');
                }
            }
            Db::commit(); 
            $insert_id=1; 
        } catch (\Exception $e) 
        {
            // 回滚事务
            Db::rollback();
            Log::write("更新用户余额失败，回滚操作".$e->getMessage(),"error");
            return -1;

        }
        return $insert_id;
    }

    /**
     * 余额操作,调用此方法务必放入事务里面
     * 
     * @param   int        $user_id                 用户ID
     * @param   int        $type                    流水类型 1 增加 2消耗
     * @param   float      $balance_num             余额数
     * @param   string     $table_id                订单号，或者表名
     * @param   int        $balance_type_id         余额流水类型 
     * @param   string     $remark                  交易描述
     * @return  bool                                true 执行成功 false 执行失败  
     */
    private function update_user_balance($user_id=0,$type=0,$balance_num=0,$table_id='',$balance_type_id=0,$remark='')
    {
        /*判断是否执行成功*/
        $yesno=false;
        $update_num=-1;
        if((int)$user_id<=0 || (int)$type<=0 ||  (float)$balance_num<0 || (int)$balance_type_id<=0)
        {
            return $yesno;
        }

        /** 加入防重 幂等 一秒内 取消重复操作 */
        $field='user_id';
        $where=[['user_id','=',$user_id],['table_id','=',$table_id],['create_time','=',time()],
                ['balance_type_id','=',$balance_type_id],['from_type','=',1]];
        $return_balance_array=Db::name($this->user_balance_table)
                            ->field($field)
                            ->where($where)
                            ->find();
        if(!empty($return_balance_array['user_id']))
        {
            return $yesno;
        }
         

        /** 获取当前用户余额，并上行锁 */
        $field='user_balance';
        $where=[['user_id','=',$user_id],['user_yesno','=',1]];
        $return_user_array=Db::name($this->user_table)
                            ->field($field)
                            ->where($where)
                            ->lock(true)
                            ->find();
        if(empty($return_user_array['user_balance']))
        {
            return $yesno;
        }

        /** 执行相关插入、更新操作 */
        $num_before=$return_user_array['user_balance'];
        if($type===1)
        {
         $num_after=bcadd($num_before,$balance_num,2);
        }
        if($type===2)
        {
         $num_after=bcsub($num_before,$balance_num,2);
        }
        $insert_data=['user_id'=>$user_id,'num'=>$balance_num, 'balance_type_id'=>$balance_type_id,
                        'type'=>$type,'create_time'=>time(), 'table_id'=>$table_id,'remark'=>$remark,
                        'num_before'=>$num_before,'num_after'=>$num_after];
        $insert_id_num=$this->add_data($this->user_balance_table,$insert_data);

        /** 插入失败直接返回 */
        if((int)$insert_id_num<=0)
        {
            return $yesno;
        }

         /** 增加余额 */
        if((int)$type===1)
        {
            $update_num=Db::name($this->user_table)
                            ->where('user_id', $user_id)
                            ->setInc('user_balance',$balance_num); 
        }
         /** 消耗余额 */
        if((int)$type===2)
        {
            $update_num=Db::name($this->user_table)
                            ->where('user_id', $user_id)
                            ->where([['user_balance','>=',$balance_num]])
                            ->setDec('user_balance',$balance_num);
        }
         /** 插入失败直接返回 */
         if((int)$update_num<=0)
         {
            return $yesno;
         }
         return true;
    }


    /**
     * 金币操作,调用此方法务必放入事务里面
     * 
     * @param   int        $user_id                 用户ID
     * @param   int        $type                    流水类型 1 增加 2消耗
     * @param   float      $coin_num                金币数
     * @param   string     $table_id                订单号，或者表名
     * @param   int        $coin_type_id            金币流水类型 
     * @param   string     $remark                  交易描述
     * @return  bool                                true 执行成功 false 执行失败  
     */
    private function update_user_coin($user_id=0,$type=0,$coin_num=0,$table_id='',$coin_type_id=0,$remark='')
    {
        /*判断是否执行成功*/
        $yesno=false;
        $update_num=-1;
        if((int)$user_id<=0 || (int)$type<=0 ||  (float)$coin_num<0 || (int)$coin_type_id<=0)
        {
            return $yesno;
        }

        /** 加入防重 幂等 一秒内 取消重复操作 */
        $field='user_id';
        $where=[['user_id','=',$user_id],['table_id','=',$table_id],['create_time','=',time()],
                ['coin_type_id','=',$coin_type_id],['from_type','=',1]];
        $return_coin_array=Db::name($this->user_coin_table)
                            ->field($field)
                            ->where($where)
                            ->find();
        if(!empty($return_coin_array['user_id']))
        {
            return $yesno;
        }
         

        /** 获取当前用户金币，并上行锁 */
        $field='user_gold';
        $where=[['user_id','=',$user_id],['user_yesno','=',1]];
        $return_user_array=Db::name($this->user_table)
                            ->field($field)
                            ->where($where)
                            ->lock(true)
                            ->find();
        if(empty($return_user_array['user_gold']))
        {
            return $yesno;
        }

        /** 执行相关插入、更新操作 */
        $num_before=$return_user_array['user_gold'];
        if($type===1)
        {
         $num_after=bcadd($num_before,$coin_num,2);
        }
        if($type===2)
        {
         $num_after=bcsub($num_before,$coin_num,2);
        }
        $insert_data=['user_id'=>$user_id,'num'=>$coin_num, 'coin_type_id'=>$coin_type_id,
                        'type'=>$type,'create_time'=>time(), 'table_id'=>$table_id,'remark'=>$remark,
                        'num_before'=>$num_before,'num_after'=>$num_after];
        $insert_id_num=$this->add_data($this->user_coin_table,$insert_data);

        /** 插入失败直接返回 */
        if((int)$insert_id_num<=0)
        {
            return $yesno;
        }

         /** 增加金币 */
        if((int)$type===1)
        {
            $update_num=Db::name($this->user_table)
                            ->where('user_id', $user_id)
                            ->setInc('user_gold',$coin_num); 
        }
         /** 消耗金币 */
        if((int)$type===2)
        {
            $update_num=Db::name($this->user_table)
                            ->where('user_id', $user_id)
                            ->where([['user_gold','>=',$coin_num]])
                            ->setDec('user_gold',$coin_num);
        }
         /** 插入失败直接返回 */
         if((int)$update_num<=0)
         {
            return $yesno;
         }
         return true;
    }


    /**
     * 更新锁定的余额、金币，供外部使用
     * @param   int        $user_id                 用户ID
     * @param   int        $user_identity_id        身份关联表ID,可以为空
     * @param   int        $type                    流向 1锁定 2 解锁
     * @param   int        $pay_type                支付类型，1 余额 2金币 3余额与金币
     * @param   float      $balance_num             余额数 单位:元
     * @param   float      $coin_num                金币数 单位:元
     * @param   float      $oper_type               操作类型，默认为1 备用 
     * @param   string     $table_id                订单号，或者表名
     * @param   string     $remark                  交易描述
     * @param   int        $update_type             是否更新账户的余额、金币 1.更新 2 不更新
     * @param   string     $data_id                 备选订单号，或者其他唯一区别的字段
     * @return                                      返回记录ID     返回-1为失败了          
     */
    public function public_update_user_lockvirtual($user_id=0,$user_identity_id=0,$type=0,$pay_type=0,$balance_num=0,$coin_num=0,
                                                    $oper_type=0,$table_id='',$remark="",$update_type=0,$data_id='')
    {
        $insert_id  = -1;
        if(empty($user_id)||empty($pay_type)||empty($type))
        {
            return $insert_id;
        }
        //配置项目类型说明
        //商品
        if($type==1)
        {
            $balance_type=$this->user_balance_type['rebate_lock'];
            $coin_type=$this->user_coin_type['rebate_lock'];
        
        }
        if($type==2)
        {
            $balance_type=$this->user_balance_type['rebate_unlock'];
            $coin_type=$this->user_coin_type['rebate_unlock'];
        }

       //支付方式判别
       if(!in_array((int)$pay_type,[1,2,3]))
       {
           return -1; 
       }

        /** 更新金额、金币，还是两者一起 */
        if($balance_num>0)
        {
            $pay_type=1;  
        }
        if($coin_num>0)
        {
            $pay_type=2;  
        }
        if($coin_num>0 && $balance_num>0)
        {
            $pay_type=3;  
        }
       
       Db::startTrans();
       try{
           
           /** 余额操作 */
           if($pay_type===1)
           {
               $yesno=$this->update_user_lock_balance($user_id,$type,$balance_num,$table_id,$balance_type,$remark,$update_type,$data_id);
               if(!$yesno)
               {
                   throw new \Exception('更新用户虚拟财务失败，回滚操作');
               }
           }

           /** 金币操作 */
           if($pay_type===2)
           {
               $yesno=$this->update_user_lock_coin($user_id,$type,$coin_num,$table_id,$coin_type,$remark,$update_type,$data_id);
               if(!$yesno)
               {
                   throw new \Exception('更新用户虚拟财务失败，回滚操作');
               }
           }

            /** 余额金币同时操作 */
           if($pay_type===3)
           {
               $yesno_balance=$this->update_user_lock_balance($user_id,$type,$balance_num,$table_id,$balance_type,$remark,$update_type,$data_id);
               $yesno_coin=$this->update_user_lock_coin($user_id,$type,$coin_num,$table_id,$coin_type,$remark,$update_type,$data_id);
               if(!$yesno_balance || !$yesno_coin)
               {
                   throw new \Exception('更新用户虚拟财务失败，回滚操作');
               }
           }
           Db::commit(); 
           $insert_id=1; 
       } catch (\Exception $e) 
       {
           // 回滚事务
           Db::rollback();
           Log::write("更新用户余额失败，回滚操作".$e->getMessage(),"error");
           return -1;

       }
       return $insert_id;
    }



    /**
     * 锁定余额操作,调用此方法务必放入事务里面
     * 
     * @param   int        $user_id                 用户ID
     * @param   int        $type                    流水类型 1 增加 2消耗
     * @param   float      $lock_balance_num        锁定余额数
     * @param   string     $table_id                订单号，或者表名
     * @param   int        $balance_type_id         余额流水类型 
     * @param   string     $remark                  交易描述
     * @param   int        $yesno_update            是否需要更新余额  1.更新 2 不更新
     * @return  bool                                true 执行成功 false 执行失败  
     */
    public function update_user_lock_balance($user_id=0,$type=0,$lock_balance_num=0,$table_id='',$balance_type_id=0,$remark='',$yesno_update=1,$data_id='')
    {
        /*判断是否执行成功*/
        $yesno=false;
        $update_num=-1;
        if((int)$user_id<=0 || (int)$type<=0 ||  (float)$lock_balance_num<0 || (int)$balance_type_id<=0)
        {
            return $yesno;
        }

        /** 加入防重 幂等 一秒内 取消重复操作 */
        $field='user_id';
        $where=[['user_id','=',$user_id],['table_id','=',$table_id],['create_time','=',time()],['balance_type_id','=',$balance_type_id]];
        $return_balance_array=Db::name($this->user_balance_table)
                            ->field($field)
                            ->where($where)
                            ->find();
        if(!empty($return_balance_array['user_id']))
        {
            return $yesno;
        }
         

        /** 获取当前用户锁定余额，并上行锁 */
        $field='user_balance_lock_num,user_id';
        $where=[['user_id','=',$user_id],['user_yesno','=',1]];
        $return_user_array=Db::name($this->user_table)
                            ->field($field)
                            ->where($where)
                            ->lock(true)
                            ->find();
        if(empty($return_user_array['user_id']))
        {
            return $yesno;
        }

        /** 执行相关插入、更新操作 */
        $num_before=$return_user_array['user_balance_lock_num'];
        if($type===1)
        {
         $num_after=bcadd($num_before,$lock_balance_num,2);
        }
        if($type===2)
        {
         $num_after=bcsub($num_before,$lock_balance_num,2);
        }
        $insert_data=['user_id'=>$user_id,'num'=>$lock_balance_num, 'balance_type_id'=>$balance_type_id,
                        'type'=>$type,'create_time'=>time(), 'table_id'=>$table_id,'remark'=>$remark,'from_type'=>2,
                        'num_before'=>$num_before,'num_after'=>$num_after,'data_id'=>$data_id];
        $insert_id_num=$this->add_data($this->user_balance_table,$insert_data);

        /** 插入失败直接返回 */
        if((int)$insert_id_num<=0)
        {
            return $yesno;
        }

         /** 增加锁定余额 */
        if((int)$type===1)
        {
            $update_num=Db::name($this->user_table)
                            ->where('user_id', $user_id)
                            ->setInc('user_balance_lock_num',$lock_balance_num); 
        }
         /** 消耗锁定余额 */
        if((int)$type===2)
        {
            $update_num=Db::name($this->user_table)
                            ->where('user_id', $user_id)
                            ->where([['user_balance_lock_num','>=',$lock_balance_num]])
                            ->setDec('user_balance_lock_num',$lock_balance_num);
            
            /** 如果为更新余额，此执行余额相关操作 */
            if($yesno_update===1)
            {
                $update_lock_num=$this->update_user_balance($user_id,1,$lock_balance_num,$table_id,$this->user_balance_type['rebate_unlock'],$remark);
                if((int)$update_lock_num<=0)
                {
                   return $yesno;
                }
            }                
        }
         /** 插入失败直接返回 */
         if((int)$update_num<=0)
         {
            return $yesno;
         }
         return true;
    }


    /**
     * 锁定金币操作,调用此方法务必放入事务里面
     * 
     * @param   int        $user_id                 用户ID
     * @param   int        $type                    流水类型 1 增加 2消耗
     * @param   float      $lock_coin_num           锁定金币数
     * @param   string     $table_id                订单号，或者表名
     * @param   int        $coin_type_id            金币流水类型 
     * @param   string     $remark                  交易描述
     * @param   int        $yesno_update            是否需要更新金币  1.更新 2 不更新
     * @return  bool                                true 执行成功 false 执行失败  
     */
    public function update_user_lock_coin($user_id=0,$type=0,$lock_coin_num=0,$table_id='',$coin_type_id=0,$remark='',$yesno_update=1,$data_id='')
    {
        /*判断是否执行成功*/
        $yesno=false;
        $update_num=-1;
        if((int)$user_id<=0 || (int)$type<=0 ||  (float)$lock_coin_num<0 || (int)$coin_type_id<=0)
        {
            return $yesno;
        }

        /** 加入防重 幂等 一秒内 取消重复操作 */
        $field='user_id';
        $where=[['user_id','=',$user_id],['table_id','=',$table_id],['create_time','=',time()],['coin_type_id','=',$coin_type_id]];
        $return_coin_array=Db::name($this->user_coin_table)
                            ->field($field)
                            ->where($where)
                            ->find();
        if(!empty($return_coin_array['user_id']))
        {
            return $yesno;
        }
         

        /** 获取当前用户锁定余额，并上行锁 */
        $field='user_gold_lock_num,user_id';
        $where=[['user_id','=',$user_id],['user_yesno','=',1]];
        $return_user_array=Db::name($this->user_table)
                            ->field($field)
                            ->where($where)
                            ->lock(true)
                            ->find();
        if(empty($return_user_array['user_id']))
        {
            return $yesno;
        }

        /** 执行相关插入、更新操作 */
        $num_before=$return_user_array['user_gold_lock_num'];
        if($type===1)
        {
         $num_after=bcadd($num_before,$lock_coin_num,2);
        }
        if($type===2)
        {
         $num_after=bcsub($num_before,$lock_coin_num,2);
        }
        $insert_data=['user_id'=>$user_id,'num'=>$lock_coin_num, 'coin_type_id'=>$coin_type_id,
                        'type'=>$type,'create_time'=>time(), 'table_id'=>$table_id,'remark'=>$remark,'from_type'=>2,
                        'num_before'=>$num_before,'num_after'=>$num_after,'data_id'=>$data_id];
        $insert_id_num=$this->add_data($this->user_coin_table,$insert_data);

        /** 插入失败直接返回 */
        if((int)$insert_id_num<=0)
        {
            return $yesno;
        }

         /** 增加锁定余额 */
        if((int)$type===1)
        {
            $update_num=Db::name($this->user_table)
                            ->where('user_id', $user_id)
                            ->setInc('user_gold_lock_num',$lock_coin_num); 
        }
         /** 消耗锁定余额 */
        if((int)$type===2)
        {
            $update_num=Db::name($this->user_table)
                            ->where('user_id', $user_id)
                            ->where([['user_gold_lock_num','>=',$lock_coin_num]])
                            ->setDec('user_gold_lock_num',$lock_coin_num);
            
            /** 如果为更新余额，此执行余额相关操作 */
            if($yesno_update===1)
            {
                $update_lock_num=$this->update_user_coin($user_id,1,$lock_coin_num,$table_id,$this->user_coin_type['rebate_unlock'],$remark);
                if((int)$update_lock_num<=0)
                {
                   return $yesno;
                }
            }                
        }
         /** 插入失败直接返回 */
         if((int)$update_num<=0)
         {
            return $yesno;
         }
         return true;
    }





}